C# supplies a this keyword that provides access to the current class instance. One possible use of the this keyword is to resolve scope ambiguity, which can arise when an incoming parameter is named identically to a data field of the type. Of course, ideally you would simply adopt a naming convention that does not result in such ambiguity; however, to illustrate this use of the this keyword, update your Motorcycle class with a new string field (named name) to represent the driver's name. Next, add a method named SetDriverName() implemented as follows:
class Motorcycle { public int driverIntensity; // New members to represent the name of the driver. public string name; public void SetDriverName(string name) { name = name; } ... }
Although this code will compile just fine, Visual Studio 2010 will display a warning message informing you that you have assigned a variable back to itself! To illustrate, update Main() to call SetDriverName() and then print out the value of the name field, you may be surprised to find that the value of the name field is an empty string!
// Make a Motorcycle with a rider named Tiny? Motorcycle c = new Motorcycle(5); c.SetDriverName("Tiny"); c.PopAWheely(); Console.WriteLine("Rider name is {0}", c.name); // Prints an empty name value!
The problem is that the implementation of SetDriverName() is assigning the incoming parameter back to itself given that the compiler assumes name is referring to the variable currently in the method scope rather than the name field at the class scope. To inform the compiler that you wish to set the current object's name data field to the incoming name parameter, simply use this to resolve the ambiguity:
public void SetDriverName(string name) { this.name = name; }
Do understand that if there is no ambiguity, you are not required to make use of the this keyword when a class wishes to access its own data fields or members, as this is implied. For example, if we rename the string data member from name to driverName (which will also require you to update your Main() method) the use of this is optional as there is no longer a scope ambiguity:
class Motorcycle { public int driverIntensity; public string driverName; public void SetDriverName(string name) { // These two statements are functionally the same. driverName = name; this.driverName = name; } ... }
Even though there is little to be gained when using this in unambiguous situations, you may still find this keyword useful when implementing class members, as IDEs such as SharpDevelop and Visual Studio 2010 will enable IntelliSense when this is specified. This can be very helpful when you have forgotten the name of a class member and want to quickly recall the definition. Consider Figure 5-2.
Figure 5-2. The IntelliSense of this
Note It is a compiler error to use the this keyword within the implementation of a static member. As you will see, static members operate on the class (not object) level, and therefore at the class level, there is no current object (thus no this)!
Another use of the this keyword is to design a class using a technique termed constructor chaining. This design pattern is helpful when you have a class that defines multiple constructors. Given the fact that constructors often validate the incoming arguments to enforce various business rules, it can be quite common to find redundant validation logic within a class's constructor set. Consider the following updated Motorcycle:
class Motorcycle { public int driverIntensity; public string driverName; public Motorcycle() { } // Redundent constructor logic! public Motorcycle(int intensity) { if (intensity > 10) { intensity = 10; } driverIntensity = intensity; } public Motorcycle(int intensity, string name) { if (intensity > 10) { intensity = 10; } driverIntensity = intensity; driverName = name; } ... }
Here, perhaps in an attempt to ensure the safety of the rider, each constructor is ensuring that the intensity level is never greater than 10. While this is all well and good, you do have redundant code statements in two constructors. This is less than ideal, as you are now required to update code in multiple locations if your rules change (for example, if the intensity should not be greater than 5).
One way to improve the current situation is to define a method in the Motorcycle class that will validate the incoming argument(s). If you were to do so, each constructor could make a call to this method before making the field assignment(s). While this approach does allow you to isolate the code you need to update when the business rules change, you are now dealing with the following redundancy:
class Motorcycle { public int driverIntensity; public string driverName; // Constructors. public Motorcycle() { } public Motorcycle(int intensity) { SetIntensity(intensity); } public Motorcycle(int intensity, string name) { SetIntensity(intensity); driverName = name; } public void SetIntensity(int intensity) { if (intensity > 10) { intensity = 10; } driverIntensity = intensity; } ... }
A cleaner approach is to designate the constructor that takes the greatest number of arguments as the "master constructor" and have its implementation perform the required validation logic. The remaining constructors can make use of the this keyword to forward the incoming arguments to the master constructor and provide any additional parameters as necessary. In this way, you only need to worry about maintaining a single constructor for the entire class, while the remaining constructors are basically empty.
Here is the final iteration of the Motorcycle class (with one additional constructor for the sake of llustration). When chaining constructors, note how the this keyword is "dangling" off the constructor's declaration (via a colon operator) outside the scope of the constructor itself:
class Motorcycle { public int driverIntensity; public string driverName; // Constructor chaining. public Motorcycle() {} public Motorcycle(int intensity) : this(intensity, "") {} public Motorcycle(string name) : this(0, name) {} // This is the 'master' constructor that does all the real work. public Motorcycle(int intensity, string name) { if (intensity > 10) { intensity = 10; } driverIntensity = intensity; driverName = name; } ... }
Understand that using the this keyword to chain constructor calls is never mandatory. However, when you make use of this technique, you do tend to end up with a more maintainable and concise class definition. Again, using this technique you can simplify your programming tasks, as the real work is delegated to a single constructor (typically the constructor that has the most parameters), while the other constructors simply "pass the buck."
On a final note, do know that once a constructor passes arguments to the designated master constructor (and that constructor has processed the data), the constructor invoked originally by the caller will finish executing any remaining code statements. To clarify, update each of the constructors of the Motorcycle class with a fitting call to Console.WriteLine():
class Motorcycle { public int driverIntensity; public string driverName; // Constructor chaining. public Motorcycle() { Console.WriteLine("In default ctor"); } public Motorcycle(int intensity) : this(intensity, "") { Console.WriteLine("In ctor taking an int"); } public Motorcycle(string name) : this(0, name) { Console.WriteLine("In ctor taking a string"); } // This is the 'master' constructor that does all the real work. public Motorcycle(int intensity, string name) { Console.WriteLine("In master ctor "); if (intensity > 10) { intensity = 10; } driverIntensity = intensity; driverName = name; } ... }
Now, ensure your Main() method exercises a Motorcycle object as follows:
static void Main(string[] args) { Console.WriteLine("***** Fun with class Types *****\n"); // Make a Motorcycle. Motorcycle c = new Motorcycle(5); c.SetDriverName("Tiny"); c.PopAWheely(); Console.WriteLine("Rider name is {0}", c.driverName); Console.ReadLine(); }
With this, ponder the output from the previous Main() method:
***** Fun with class Types ***** In master ctor In ctor taking an int Yeeeeeee Haaaaaeewww! Yeeeeeee Haaaaaeewww! Yeeeeeee Haaaaaeewww! Yeeeeeee Haaaaaeewww! Yeeeeeee Haaaaaeewww! Yeeeeeee Haaaaaeewww! Rider name is Tiny
As you can see, the flow of constructor logic is as follows:
The nice thing about using constructor chaining, is that this programming pattern will work with any version of the C# language and .NET platform. However, if you are targeting .NET 4.0 and higher, you can further simplify your programming tasks by making use of optional arguments as an alternative to traditional constructor chaining.
In Chapter 4, you learned about optional and named arguments. Recall that optional arguments allow you to define supplied default values to incoming arguments. If the caller is happy with these defaults, they are not required to specify a unique value, however they may do so to provide the object with custom data. Consider the following version of Motorcycle which now provides a number of ways to construct objects using a single constructor definition:
class Motorcycle { // Single constructor using optional args. public Motorcycle(int intensity = 0, string name = "") { if (intensity > 10) { intensity = 10; } driverIntensity = intensity; driverName = name; } ... }
With this one constructor, you are now able to create a new Motorcycle object using zero, one, or two arguments. Recall that named argument syntax allows you to essentially skip over acceptable default settings (see Chapter 3).
static void MakeSomeBikes() { // driverName = "", driverIntensity = 0 Motorcycle m1 = new Motorcycle(); Console.WriteLine("Name= {0}, Intensity= {1}", m1.driverName, m1.driverIntensity); // driverName = "Tiny", driverIntensity = 0 Motorcycle m2 = new Motorcycle(name:"Tiny"); Console.WriteLine("Name= {0}, Intensity= {1}", m2.driverName, m2.driverIntensity); // driverName = "", driverIntensity = 7 Motorcycle m3 = new Motorcycle(7); Console.WriteLine("Name= {0}, Intensity= {1}", m3.driverName, m3.driverIntensity); }
While the use of optional/named arguments is a very slick way to streamline how you define the set of constructors used by a given class, do always remember that this syntax will lock you into compiling your software under C# 2010 and running your code under .NET 4.0. If you need to build classes which can run under any version of the .NET platform, it is best to stick to classical constructor chaining techniques.
In any case, at this point you are able to define a class with field data (aka member variables) and various members such as methods and constructors. Next up, let's formalize the role of the static keyword.